<?php

namespace Gnway\Ticket\Rest\Contracts;

use ReflectionClass;
use UnexpectedValueException;

abstract class Enum
{

  /**
   * Value
   *
   * @var mixed
   */
  private $value;

  /**
   * Declared constants list
   *
   * @var array
   */
  private static array $constants = [];

  /**
   * Enum constructor.
   *
   * @param mixed $value
   * @param bool  $strict
   * @throws UnexpectedValueException
   */
  final public function __construct($value, bool $strict = FALSE)
  {
    if (!static::contains($value, $strict)) {
      $value = $this->getTypeValue($value);
      throw new UnexpectedValueException('Value ' . $value . ' not a const in enum ' . static::class);
    }

    $this->value = $value;
  }

  /**
   * Get Enum Object
   *
   * @param mixed $value
   * @param bool  $strict
   * @return static
   */
  final public static function getEnum($value, bool $strict = FALSE): Enum
  {
    return new static($value, $strict);
  }

  /**
   * Get constants on current class or subclass
   *
   * @return array
   */
  final public static function getConstants(): array
  {
    $scope = static::class;
    if (!isset(static::$constants[$scope])) {
      static::$constants[$scope] = (new ReflectionClass(static::class))->getConstants();
    }

    return static::$constants[$scope];
  }

  /**
   * Contains
   *
   * @param mixed $value
   * @param bool  $strict
   * @return bool
   */
  final public static function contains($value, bool $strict = FALSE): bool
  {
    $constants = static::getConstants();
    if (empty($constants)) {
      return FALSE;
    }

    return in_array($value, $constants, $strict);
  }

  /**
   * Equals compare
   *
   * @param mixed $value
   * @param bool  $strict
   * @return bool
   */
  final public function equals($value, bool $strict = FALSE): bool
  {
    if ($strict) {
      return $value === $this->value;
    }

    return $value == $this->value;
  }

  /**
   * Get Value
   *
   * @return mixed
   */
  final public function getValue()
  {
    return $this->value;
  }

  /**
   * Get type and value
   *
   * @param $value
   * @return string
   */
  final protected function getTypeValue($value): string
  {
    $type = gettype($value);
    switch (strtolower($type)) {
      case 'int':
      case 'integer':
      case 'float':
      case 'double':
      case 'string':
      case 'resource':
      case 'resource (closed)':
      case 'unknown type':
        return sprintf('"%s" (%s)', $value, $type);
      case 'bool':
      case 'boolean':
        return sprintf('"%s" (%s)', $value ? 'true' : 'false', $type);
      case 'array':
        return sprintf('"Array" (%s)', $type);
      case 'callable':
        return sprintf('"Callable" (%s)', $type);
      case 'null':
        return sprintf('"null" (%s)', $type);
      case 'object':
        return sprintf('"%s" (%s)', get_class($value), $type);
      default:
        throw new UnexpectedValueException(sprintf('TypeError: No such built-in type %s.', $type));
    }
  }

  /**
   * Echo a object
   *
   * @return string
   */
  final public function __toString()
  {
    return $this->getTypeValue($this->getValue());
  }

  /**
   * On var_dump a object
   *
   * @return array
   */
  final public function __debugInfo()
  {
    return [
      '_value'     => $this->getValue(),
      '_constants' => static::getConstants(),
    ];
  }
}